package io.kwdb.sdk;

import static org.junit.jupiter.api.Assertions.*;

import io.kwdb.sdk.internal.SecurityUtil;
import java.io.IOException;
import java.net.http.HttpRequest;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import org.junit.jupiter.api.Test;

/**
 * Test cases for security headers and authentication. Validates proper header generation and
 * compression.
 */
public class SecurityHeadersTest {

  @Test
  public void testBearerTokenHeader() {
    Config config =
        Config.builder().host("localhost").httpPort(9091).authToken("test-token-12345").build();

    HttpRequest.Builder builder = HttpRequest.newBuilder();
    SecurityUtil.configureHttpSecurity(builder, config, true);

    // Build request and check headers
    HttpRequest request = builder.uri(java.net.URI.create("http://test")).build();

    assertTrue(request.headers().map().containsKey("authorization"));
    String authHeader = request.headers().firstValue("authorization").orElse("");
    assertEquals("Bearer test-token-12345", authHeader);
  }

  @Test
  public void testBasicAuthHeader() {
    Config config =
        Config.builder()
            .host("localhost")
            .httpPort(9091)
            .username("testuser")
            .password("testpass")
            .build();

    HttpRequest.Builder builder = HttpRequest.newBuilder();
    SecurityUtil.configureHttpSecurity(builder, config, true);

    // Build request and check headers
    HttpRequest request = builder.uri(java.net.URI.create("http://test")).build();

    assertTrue(request.headers().map().containsKey("authorization"));
    String authHeader = request.headers().firstValue("authorization").orElse("");

    // Verify Basic auth encoding
    String expectedAuth = "testuser:testpass";
    String expectedEncoded =
        Base64.getEncoder().encodeToString(expectedAuth.getBytes(StandardCharsets.UTF_8));
    assertEquals("Basic " + expectedEncoded, authHeader);
  }

  @Test
  public void testNoAuthHeader() {
    Config config = Config.builder().host("localhost").httpPort(9091).build();

    HttpRequest.Builder builder = HttpRequest.newBuilder();
    SecurityUtil.configureHttpSecurity(builder, config, true);

    // Build request and check headers
    HttpRequest request = builder.uri(java.net.URI.create("http://test")).build();

    assertFalse(request.headers().map().containsKey("authorization"));
  }

  @Test
  public void testBearerTokenPriority() {
    // When both token and username/password are set, token takes priority
    Config config =
        Config.builder()
            .host("localhost")
            .httpPort(9091)
            .authToken("bearer-token")
            .username("testuser")
            .password("testpass")
            .build();

    HttpRequest.Builder builder = HttpRequest.newBuilder();
    SecurityUtil.configureHttpSecurity(builder, config, true);

    HttpRequest request = builder.uri(java.net.URI.create("http://test")).build();

    String authHeader = request.headers().firstValue("authorization").orElse("");
    assertTrue(authHeader.startsWith("Bearer "));
    assertEquals("Bearer bearer-token", authHeader);
  }

  @Test
  public void testGzipCompression() throws IOException {
    String testData = "This is test data that should be compressed";
    byte[] originalBytes = testData.getBytes(StandardCharsets.UTF_8);
    byte[] compressed = SecurityUtil.gzip(originalBytes);

    // Compressed should be different and likely smaller
    assertNotEquals(originalBytes.length, compressed.length);

    // Verify gzip magic number
    assertEquals((byte) 0x1f, compressed[0]);
    assertEquals((byte) 0x8b, compressed[1]);
  }

  @Test
  public void testGzipStringCompression() throws IOException {
    String testData = "Repeated data repeated data repeated data repeated data";
    byte[] compressed = SecurityUtil.gzip(testData);

    // For repeated data, compression should be effective
    assertTrue(
        compressed.length < testData.length(), "Compressed size should be smaller than original");
  }

  @Test
  public void testSchemeSelection() {
    // Test HTTP
    Config httpConfig = Config.builder().host("localhost").httpPort(9091).tls(false).build();
    assertEquals("http", SecurityUtil.getScheme(httpConfig));

    // Test HTTPS
    Config httpsConfig = Config.builder().host("localhost").httpPort(9091).tls(true).build();
    assertEquals("https", SecurityUtil.getScheme(httpsConfig));
  }

  @Test
  public void testBuildIlpUrl() {
    // Test HTTP URL
    Config httpConfig = Config.builder().host("example.com").httpPort(8080).tls(false).build();
    assertEquals("http://example.com:8080/write", SecurityUtil.buildIlpUrl(httpConfig));

    // Test HTTPS URL
    Config httpsConfig =
        Config.builder().host("secure.example.com").httpPort(443).tls(true).build();
    assertEquals("https://secure.example.com:443/write", SecurityUtil.buildIlpUrl(httpsConfig));

    // Test default port
    Config defaultPortConfig = Config.builder().host("localhost").tls(false).build();
    assertEquals("http://localhost:9091/write", SecurityUtil.buildIlpUrl(defaultPortConfig));
  }

  @Test
  public void testEmptyAuthToken() {
    Config config =
        Config.builder()
            .host("localhost")
            .httpPort(9091)
            .authToken("") // Empty token should be treated as no auth
            .build();

    HttpRequest.Builder builder = HttpRequest.newBuilder();
    SecurityUtil.configureHttpSecurity(builder, config, true);

    HttpRequest request = builder.uri(java.net.URI.create("http://test")).build();

    assertFalse(request.headers().map().containsKey("authorization"));
  }

  @Test
  public void testSpecialCharactersInBasicAuth() {
    Config config =
        Config.builder()
            .host("localhost")
            .httpPort(9091)
            .username("user@example.com")
            .password("p@ss:word!123")
            .build();

    HttpRequest.Builder builder = HttpRequest.newBuilder();
    SecurityUtil.configureHttpSecurity(builder, config, true);

    HttpRequest request = builder.uri(java.net.URI.create("http://test")).build();

    String authHeader = request.headers().firstValue("authorization").orElse("");

    // Decode and verify
    String encoded = authHeader.substring("Basic ".length());
    String decoded = new String(Base64.getDecoder().decode(encoded), StandardCharsets.UTF_8);
    assertEquals("user@example.com:p@ss:word!123", decoded);
  }

  @Test
  public void testLargeDataCompression() throws IOException {
    // Create a large string with repeated patterns
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < 1000; i++) {
      sb.append("Line ").append(i).append(": This is repeated data for compression test.\n");
    }
    String largeData = sb.toString();

    byte[] compressed = SecurityUtil.gzip(largeData);

    // Compression should be very effective for repeated patterns
    double ratio = (double) compressed.length / largeData.length();
    assertTrue(ratio < 0.5, "Compression ratio should be less than 50%");
  }

  @Test
  public void testNullCaCertPath() throws Exception {
    // Should use default SSL context when CA cert path is null
    javax.net.ssl.SSLContext context = SecurityUtil.createSSLContext(null);
    assertNotNull(context);
    assertEquals("Default", context.getProvider().getName());
  }

  @Test
  public void testEmptyCaCertPath() throws Exception {
    // Should use default SSL context when CA cert path is empty
    javax.net.ssl.SSLContext context = SecurityUtil.createSSLContext("");
    assertNotNull(context);
    assertEquals("Default", context.getProvider().getName());
  }
}
